#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.
#
import csv
import struct
import time
import wave

import requests
import socket
import queue
import threading
from datetime import datetime
import os

from pyecharts.charts import Line
from pyecharts import options as opts
import webbrowser


# AcquisitionSetup类  实现采集器内置Webserver的访问并完成采样率的设置
class AcquisitionSetup:
    # 通过request访问采集器内置Webserver,完成采样率的设置
    class_variable = "AcquisitionSetup_class"

    # 类的构造方法
    def __init__(self, file_path):
        # 判断采集器是否在线，默认不在线
        self.Status = False
        # 定义文件保存路径
        self.File_path = file_path
        # 检查路径
        self.check_path(file_path)

        # 采集器的默认网络配置，可根据项目配置。配置前需对网络有一定了解，建议默认。
        self.IP_Address = '10.9.132.110'
        self.Subnet_Mask = '255.255.255.0'
        self.Gateway_Address = '10.9.132.1'

        # 本地电脑网络配置，可根据项目配置。配置前需对网络有一定了解，建议默认。
        self.Remote_IP_Address = '10.9.0.94'
        self.Remote_port = 6110

        # 采样率配置2的N次方 最大值不超过200kHz
        self.sampleRate = 51200

    # 采样率配置，默认值为51200
    def sample_set(self, samplerate=51200):
        print('% DeepDaq_INFO : Welcome!,trying to connect DeepDaq...')
        # 判断设置采样率是否满足要求
        if 64 <= samplerate <= 102400 and samplerate % 2 == 0:
            pass
        else:
            print('% DeepDaq_ERROR :' + '设备采样率需大于64Hz,小于102400Hz')
            return
        try:
            url = 'http://10.9.132.110/ip.cgi?'
            headers = {
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,'
                          '*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
                'Host': self.IP_Address,
                'Connection': 'close',
                'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, '
                              'like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36 Edg/121.0.0.0'
            }
            params = {
                'IPaddr': self.IP_Address,
                'MaskAddr': self.Subnet_Mask,
                'GateWayAddr': self.Gateway_Address,
                'RemoteIPaddr': self.Remote_IP_Address,
                'RemotePort': self.Remote_port,
                'Capture': samplerate
            }
            response = requests.get(url=url, headers=headers, params=params)
            if response.status_code != 200:
                print('% DeepDaq_ERROR : 与采集器建立通讯失败，检查IP配置和网络连接。')
            else:
                self.Status = True
                self.sampleRate = samplerate
                print('% DeepDaq_INFO :' + ' 通讯成功，等待系统进行配置(耗时约9s)...')
                # 采用request提交配置信息时会通过广播的形式触发Windows系统的LLDP（Link Layer Discovery Protocol）局域网协议，
                # 阻塞数据采集线程，所以time.sleep（12）是为了线程的安全，防止数据的丢失。
                for n in range(3):
                    print('......')
                    time.sleep(3)
                print('% DeepDaq_INFO :' + ' 采样率配置成功 ' + str(samplerate) + 'Hz')
        except Exception as e:
            print('% DeepDaq_ERROR :' + str(e))
        return self.Status

    def check_path(self, file_path):
        # 检查文件夹是否存在
        if not os.path.exists(file_path):
            # 不存在则创建文件夹
            os.makedirs(file_path)
            print(f"% DeepDaq_INFO :文件保存路径： '{file_path}'")
        else:
            print(f"% DeepDaq_INFO :文件保存路径： '{file_path}'")


class ChannelSetup:
    # 类 ChannelSetup 提供通道设置的函数
    # 通道设置包含:
    # 通道 status: ON/OFF
    # input_mode 选项：Voltage DC, IEPE(ICP)
    # 测试量（Measured_Quantity） 选项：Acceleration, Acoustics, Voltage
    # 传感器灵敏度 sensitivity
    # 灵敏度单位 mv/g, mv/pa, mv/V
    # 量程 range 选项：10V，5V

    # 类的属性
    class_variable = "完成通道设置"

    # 类的构造方法
    def __init__(self, number):

        # 通道配置，可根据项目配置。配置前需对采集器有一定了解，建议默认。
        # 通道选择
        self.Number = number
        # 配置结果，默认为空
        self.Result = {}
        # 默认全开，该功能为预留功能
        self.Status = 'ON'
        # 默认IEPE，该功能为预留功能
        self.Input_mode = 'IEPE'
        # 默认Acceleration，保存为CSV等文件时将该信息写入文件
        self.Measured_Quantity = 'Acceleration'
        # 默认50 保存为CSV等文件时将该信息写入文件
        self.Sensitivity = 50
        # 默认'mv/g' 保存为CSV等文件时将该信息写入文件
        self.Sensitivity_unit = 'mv/g'
        # 通道满量程电压，最大为10V
        self.Range = 10
        # 采集器电器特性，默认值
        self.Bias = 55.5
        # 采集器电器特性，默认值。
        self.Gain = 1

    def channel_set(self, input_mode='IEPE', measured_quantity='Acceleration', sensitivity=50, sensitivity_unit='mv/g',
                    channel_range=10, bias=55.5, gain=1):
        try:
            self.Input_mode = input_mode
            self.Measured_Quantity = measured_quantity
            self.Sensitivity = sensitivity
            self.Sensitivity_unit = sensitivity_unit
            self.Range = channel_range
            self.Bias = bias
            # 采集器电器特性，默认值。
            self.Gain = gain
            # 为配置结果创建字典
            self.Result = {'channel_number': self.Number, 'input_mode': self.Input_mode,
                           'measured_quantity': measured_quantity, 'sensitivity': sensitivity,
                           'sensitivity_unit': sensitivity_unit, 'channel_range': channel_range,
                           'bias': self.Bias, 'gain': gain}
            # print(self.Result)
        except Exception as e:
            print('% DeepDaq_ERROR :' + str(e))
        return self.Result


class Measure:

    # 类Measure 实现数据的测量,需要传入一个AcquisitionSetup对象
    def __init__(self, acquisition_setup, *args):
        # 检查参数是否正确
        if not isinstance(acquisition_setup, AcquisitionSetup):
            raise ValueError("% DeepDaq_ERROR :参数acquisition不为空且是AcquisitionSetup的实例")

        if not args:  # 判断是否为空
            raise ValueError("% DeepDaq_ERROR :至少传入一个通道配置")

        # 类的构造方法,构建Measure对象时，直接初始化socket 并绑定IP和端口
        self.sample_rate = acquisition_setup.sampleRate

        self.file_path = acquisition_setup.File_path
        # 将采集配置中的Remote_IP_Address 传给测量ip
        self.ip = acquisition_setup.Remote_IP_Address
        # 将采集配置中的Remote_port传给测量port
        self.port = acquisition_setup.Remote_port
        # 默认8个通道全开，预留功能
        self.Channel_list = args
        # 创建socket 用于UDP数据获取
        self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.server_address = (self.ip, self.port)
        # socket 绑定IP和端口
        self.udp_socket.bind(self.server_address)
        # 初始化队列缓存
        self.data_queue = queue.Queue()
        # 采集超时设置，默认1.0秒
        self.udp_socket.settimeout(2.0)

        # 全局变量，用于退出线程
        self.exit_flag = threading.Event()
        # 使用 threading.Lock 来确保对共享资源的线程安全访问
        self.lock = threading.Lock()

        # 启动数据接受线程
        self.receive_thread = threading.Thread(target=self.receive_data)
        self.receive_thread.start()

    # ##################################分割线###################################
    # 启动数据存储至bin文件
    def receive_data(self):
        try:
            while not self.exit_flag.is_set():
                # 数据获取，数据长度1293字节，采集器协议决定，固定值。
                data = self.udp_socket.recv(1293)
                # 数据写入缓存队列
                self.data_queue.put(data)
                # print(f"Received data:{data}")
        except KeyboardInterrupt:
            pass
        except socket.timeout:
            print('% DeepDaq_ERROR : 未能接收到数据或存储空间不足')
            pass
        except socket.error as e:
            print(f"% DeepDaq_ERROR : {e}")
            pass
        finally:
            self.udp_socket.close()
            print("% DeepDaq_INFO : 采集结束...")

    def save_to_bin(self):
        # 启动数据存储至bin文件
        # 采用时间定义文件名的方法，具体到秒
        print(f"% DeepDaq_INFO : 开始采集...")
        current_time = datetime.now()
        formatted_time = current_time.strftime("%Y-%m-%d_%H-%M-%S")  # 使用下划线替代非法字符
        file_path = os.path.join(self.file_path, f"data_{formatted_time}.bin")
        self.file_path = file_path
        try:
            # 启动数据写入线程
            this_thread = threading.Thread(target=self.write_to_bin, args=(file_path,))
            this_thread.start()
        except Exception as e:
            print(f"% DeepDaq_ERROR : {e} 数据存储失败,退出采集...")
        finally:
            pass

    def write_to_bin(self, file_path):
        with open(file_path, "ab") as file:
            try:
                while not self.exit_flag.is_set():
                    with self.lock:
                        data = self.data_queue.get(timeout=2)
                        # print(f"Stored to file: {data}")
                        file.write(data)
            except queue.Empty as e:
                print(f'{e}')
            # break
        print("% DeepDaq_INFO : 数据停止保存... ")

    # ##################################分割线###################################
    # 启动数据存储至CSV文件。
    def save_as_csv(self):
        # 将二进制文件转换为CSV文件时保持文件名一致
        csv_file_path = self.file_path.replace('.bin', '.csv')
        # 创建CSV文件并将CH1.CH2....CH8写入头部
        with open(csv_file_path, 'w', newline='') as csvfile:
            csv_writer = csv.writer(csvfile)
            csv_writer.writerow([f"{channel_list}" for channel_list in self.Channel_list])

        # 读取需要解析的二进制文件
        with open(self.file_path, 'rb') as file:
            while True:
                # 读取前两个字节，检查帧头
                frame_header = file.read(2)
                if not frame_header:
                    break  # 文件读取完毕
                if frame_header != b'\xa5\x5a':
                    continue  # 不是帧头，继续查找
                # 读取数据缓存长度、数据类型、数据总字节数、通道数,共计9个字节
                length, data_type, total_bytes, channel_count = struct.unpack('<IBHH', file.read(9))
                # 读取数据字节共计80*8*2，去掉最后两个数据结束标志字节。
                data = file.read(total_bytes - 2)
                channel_data = self.parse_binary_data(data)  # channel_data 包含了8组通道数据 数据范围-32768~32768

                # 保留了传入的通道设置,并且数据转化位模拟量 ，数据范围 channel_range/32768*1000/sensitivity 例：±（10V / 32768 * 1000mv/V / 50mv/g）
                channel_data = self.convert_by_channel_set(channel_data,'csv')

                # 数据写入CSV文件
                self.write_to_csv(channel_data, csv_file_path)
                # 检查结尾标识符
                end_marker = file.read(2)
                # print(end_marker)
                if end_marker != b'\x00\x16':
                    print("Error: Missing end marker")
                    break
        print('% DeepDaq_INFO : 数据另存为CSV ')

    def parse_binary_data(self, data):  # 二进制数据解析
        # 每两个字节为一个小端数据
        # 8个通道的数据交替排列
        channel_data = [struct.unpack('<h', data[i + j:i + j + 2])[0] for i in range(0, len(data), 16) for j in
                        range(0, 16, 2)]
        # 每通道数据放入一个列表
        channels = [channel_data[i::8] for i in range(8)]
        return channels

    def convert_by_channel_set(self, channels_data, tag ):  # 根据通道设置将数字量转化为模拟量
        convert_data = []  # 定义一个空列表用于存储转换后数据
        if tag =='csv':  # 如果要存储到csv
            for channel_list in self.Channel_list:
                # 获取数据通道
                channel_number = channel_list['channel_number']
                # 获取灵敏度
                sensitivity = channel_list['sensitivity']
                # 获取通道量程
                channel_range = channel_list['channel_range']
                # gain
                gain = channel_list['gain']
                k = gain*channel_range/32768*1000/sensitivity
                # bias
                bias = channel_list['bias']
                # 转化
                result = [(data-bias)*k for data in channels_data[channel_number-1]]
                convert_data.append(result)
        elif tag == 'wav': # 如果要存储到wav
            for channel_list in self.Channel_list:
                # 获取数据通道
                channel_number = channel_list['channel_number']
                # gain
                gain = channel_list['gain']
                k = gain
                # bias
                bias = round(channel_list['bias'])
                # 转化
                result = [(data-bias)*k for data in channels_data[channel_number-1]]
                # 使用列表推导式来修改数组
                result_limit = [32768 if x > 32768 else -32768 if x < -32768 else x for x in result]
                convert_data.append(result_limit)
        else:
            pass
        return convert_data

    def write_to_csv(self, channels, csv_filename):
        with open(csv_filename, 'a', newline='') as csvfile:
            csv_writer = csv.writer(csvfile)
            # 逐行写入数据
            for row_data in zip(*channels):
                csv_writer.writerow(row_data)

    # ##################################分割线###################################
    # 启动数据存储至.wav文件
    def save_as_wav(self):
        # wav 文件不能通过追加的形式不断向文件写入数据，因此需要一次性将二进制文件bin中的数据写入数组。
        channels_data = [[], [], [], [], [], [], [], []]  # 定义8通道的一个数组列表
        # 读取需要解析的二进制文件写入数组列表，该部分处理了所有8通道数据
        with open(self.file_path, 'rb') as file:
            while True:
                # 读取前两个字节，检查帧头
                frame_header = file.read(2)
                if not frame_header:
                    break  # 文件读取完毕
                if frame_header != b'\xa5\x5a':
                    continue  # 不是帧头，继续查找
                # 读取数据缓存长度、数据类型、数据总字节数、通道数,共计9个字节
                length, data_type, total_bytes, channel_count = struct.unpack('<IBHH', file.read(9))
                # 读取数据字节共计80*8*2，去掉最后两个数据结束标志字节。
                data = file.read(total_bytes - 2)
                channel_data = self.parse_binary_data(data)  # channel_data 包含了8组通道十进制数据
                # 数组的追加写入
                for sub_a, sub_b in zip(channels_data, channel_data):
                    sub_a.extend(sub_b)
                # 检查结尾标识符
                end_marker = file.read(2)
                # print(end_marker)
                if end_marker != b'\x00\x16':
                    print("Error: Missing end marker")
                    break

        # 根据通道设置处理数据并准备将设置的数据写入wav文件，返回的数据仅包含设置的通道数据
        channels_data_processed = self.convert_by_channel_set(channels_data, 'wav')

        # 将二进制文件转换为wav文件时保持文件名一致
        wav_file_path = self.file_path.replace('.bin', '.wav')
        # 获取配置的通道数
        num_channels = len(self.Channel_list)
        sample_width = 2  # 16位有符号整数，每个通道2字节，固定值，与采集器16位一致，不可修改。
        frame_rate = self.sample_rate  # 例如，51200Hz

        with wave.open(wav_file_path, 'w') as wav_file:
            wav_file.setnchannels(num_channels)
            wav_file.setsampwidth(sample_width)
            wav_file.setframerate(frame_rate)
            wav_file.setnframes(len(channels_data_processed[0]))

            for channel_data_processed in zip(*channels_data_processed):
                packed_frame = struct.pack('<' + 'h' * num_channels, *channel_data_processed)
                wav_file.writeframes(packed_frame)
        print('% DeepDaq_INFO : 数据另存为wav ')

    def close_socket(self):

        # 关闭 socket
        self.udp_socket.close()


class Post_Processing:

    def __init__(self,acquisition_setup, *args):
        # 检查参数是否正确
        if not isinstance(acquisition_setup, AcquisitionSetup):
            raise ValueError("% DeepDaq_ERROR :需传入一个AcquisitionSetup的对象")

        if not args:  # 判断是否为空
            raise ValueError("% DeepDaq_ERROR :至少传入一个通道配置")


        pass

    def plot_show(self,tag):
        if tag=='web':
            pass



if __name__ == "__main__":
    #
    pass
